Solutions/Threat Intelligence (NEW)/Analytic Rules/FileHashEntity_SecurityEvent.yaml (71 lines of code) (raw):

id: 9f7dc779-1e51-4925-ae4a-db1db933077f name: TI map File Hash to Security Event description: | 'Identifies a match in Security Event data from any File Hash IOC from TI' severity: Medium requiredDataConnectors: - connectorId: SecurityEvents dataTypes: - SecurityEvent - connectorId: WindowsSecurityEvents dataTypes: - SecurityEvents - connectorId: WindowsForwardedEvents dataTypes: - WindowsEvent - connectorId: ThreatIntelligence dataTypes: - ThreatIntelligenceIndicator - connectorId: ThreatIntelligenceTaxii dataTypes: - ThreatIntelligenceIndicator - connectorId: MicrosoftDefenderThreatIntelligence dataTypes: - ThreatIntelligenceIndicator queryFrequency: 1h queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - CommandAndControl relevantTechniques: - T1071 query: | let dt_lookBack = 1h; let ioc_lookBack = 14d; ThreatIntelIndicators //extract key part of kv pair | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0))) | where isnotempty(IndicatorType) and IndicatorType == "file" | extend FileHashType = replace("'", "", substring(ObservableKey, indexof(ObservableKey, "hashes.") + 7, strlen(ObservableKey) - indexof(ObservableKey, "hashes.") - 7)) | extend FileHashValue = toupper(ObservableValue) | where isnotempty(FileHashValue) | where TimeGenerated >= ago(ioc_lookBack) | extend FileHashValue = toupper(FileHashValue) | extend IndicatorId = tostring(split(Id, "--")[2]) | extend Url = iff(ObservableKey == "url:value", ObservableValue, "") | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id | where IsActive == true and ValidUntil > now() | project-reorder *, FileHashType, FileHashValue, Type // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated | join kind=innerunique ( union isfuzzy=true (SecurityEvent | where TimeGenerated >= ago(dt_lookBack) | where EventID in ("8003","8002","8005") | where isnotempty(FileHash) | extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID, FileHash = toupper(FileHash) ), (WindowsEvent | where TimeGenerated >= ago(dt_lookBack) | where EventID in ("8003","8002","8005") | where isnotempty(EventData.FileHash) | extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID, FileHash = toupper(EventData.FileHash) ) ) on $left.FileHashValue == $right.FileHash | where SecurityEvent_TimeGenerated < ValidUntil | extend Description = tostring(parse_json(Data).description) | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels)) | summarize SecurityEvent_TimeGenerated = arg_max(SecurityEvent_TimeGenerated, *) by IndicatorId, FileHash | project SecurityEvent_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ValidUntil, Confidence, Process, FileHash, Computer, Account, Event, FileHashValue, FileHashType, Url | extend NTDomain = tostring(split(Account, '\\', 0)[0]), Name = tostring(split(Account, '\\', 1)[0]) | extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')) | extend timestamp = SecurityEvent_TimeGenerated entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: Account - identifier: Name columnName: Name - identifier: NTDomain columnName: NTDomain - entityType: Host fieldMappings: - identifier: FullName columnName: Computer - identifier: HostName columnName: HostName - identifier: DnsDomain columnName: DnsDomain - entityType: URL fieldMappings: - identifier: Url columnName: Url - entityType: FileHash fieldMappings: - identifier: Value columnName: FileHashValue - identifier: Algorithm columnName: FileHashType version: 1.4.7 kind: Scheduled